package com.zhuangjie.shop.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhaungjie.base.conduit.TypeR;
import com.zhaungjie.base.utils.*;
import com.zhaungjie.base.utils.LambdaStructureParser.LambdaStructureParser;
import com.zhaungjie.base.utils.PropertiesCopyUtils.PropertiesCopy;
import com.zhuangjie.base.constant.CommonSeparatorConstants;
import com.zhuangjie.base.entity.LocalDateRange;
import com.zhuangjie.base.entity.TimeRange;
import com.zhuangjie.base.enums.VerificationCodeScenes;
import com.zhuangjie.coupon.dto.QueryConfirmRequireDTO;
import com.zhuangjie.coupon.enums.CouponType;
import com.zhuangjie.coupon.enums.IssuedType;
import com.zhuangjie.coupon.pojo.CouponCoreInfo;
import com.zhuangjie.coupon.vo.ApplicableShopCouponCoreInfoVO;
import com.zhuangjie.coupon.vo.CouponCoreInfoAndPreferentialAmount;
import com.zhuangjie.coupon.vo.UseCouponCoreInfoContainer;
import com.zhuangjie.mvc.enhance.mp_query_enhance.LambdaQueryWrapperPlus;
import com.zhuangjie.mvc.enhance.mp_query_enhance.LambdaQueryWrapperPlusHelper;
import com.zhuangjie.mvc.enhance.mp_query_enhance.enums.AggregationOperation;
import com.zhuangjie.mvc.future.ContextAwareExecutor;
import com.zhuangjie.mvc.helper.GeoHelper.GeoCoordinateUtils;
import com.zhuangjie.mvc.helper.GeoHelper.GeoCoordinates;
import com.zhuangjie.order.entity.OrderActivity;
import com.zhuangjie.order.vo.ShopInfoForOrder;
import com.zhuangjie.redis.service.PhoneCodeRedisService;
import com.zhuangjie.security.intercepor.UserRequestInterceptor;
import com.zhuangjie.security.provider.UserProvider;
import com.zhuangjie.shop.constant.ActivityConstant;
import com.zhuangjie.shop.constant.ShopConstant;
import com.zhuangjie.shop.dto.FollowShopPageDTO;
import com.zhuangjie.shop.dto.SearchNearbyStoresPageDTO;
import com.zhuangjie.shop.dto.SettleInDTO;
import com.zhuangjie.shop.entity.*;
import com.zhuangjie.shop.enums.*;
import com.zhuangjie.shop.feign.CouponFeignService;
import com.zhuangjie.shop.feign.OrderFeignService;
import com.zhuangjie.shop.feign.UserFeignService;
import com.zhuangjie.shop.handler.activity.ActivityEnv;
import com.zhuangjie.shop.handler.activity.ActivityPreferentialSettlement;
import com.zhuangjie.shop.handler.activity.handler.ActivityEnvRequireMetaInformation;
import com.zhuangjie.shop.helper.CouponHelper;
import com.zhuangjie.shop.helper.PredetermineDatesAndTimeRangesInfoGenerator;
import com.zhuangjie.shop.helper.ShopModuleTokenDataProvider;
import com.zhuangjie.shop.mapper.ShopMapper;
import com.zhuangjie.shop.pojo.ShopAdvanceBookingDays;
import com.zhuangjie.shop.pojo.ShopBasicInfo;
import com.zhuangjie.shop.pojo.ShopBusinessConfig;
import com.zhuangjie.shop.pojo.UseCouponDetail;
import com.zhuangjie.shop.service.*;
import com.zhuangjie.shop.vo.*;
import com.zhuangjie.shop.vo.order_confirm_require.OrderConfirmRequireVO;
import com.zhuangjie.shop.vo.order_confirm_require.PreferentialInfo;
import com.zhuangjie.shop.vo.predetermine.GeneratedPredetermineDayAndTimesInfo;
import com.zhuangjie.shop.vo.shop_display.NearbyShopVO;
import com.zhuangjie.shop.vo.shop_display.ShopStatisticalForShopShow;
import com.zhuangjie.shop.vo.shop_display.ShopSurfaceInfoVO;
import com.zhuangjie.user.entity.AddressBook;
import com.zhuangjie.user.vo.AddressBookVO;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * <p>
 * 商家/商店 服务实现类
 * </p>
 *
 * @author zhuangjie
 * @since 2023-09-24
 */
@Service
@Slf4j
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements ShopService {


    @Autowired
    private ShopInfoService shopInfoService;

    @Autowired
    private MerchantAccountService merchantAccountService;

    @Autowired
    private UserRequestInterceptor userRequestInterceptor;

    @Autowired
    private DishService dishService;

    @Autowired
    private DishCategoryService dishCategoryService;

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private ContextAwareExecutor contextAwareExecutor;

    @Autowired
    private ShopBusinessRulesService shopBusinessRulesService;

    // @Qualifier("com.zhuangjie.shop.feign.UserFeignService")
    @Autowired
    private UserFeignService userFeignService;

    @Autowired
    private PhoneCodeRedisService phoneCodeRedisService;


    @Autowired
    private ShoppingCartService shoppingCartService;

    @Autowired
    private ShopActivityInstanceService shopActivityInstanceService;

    @Autowired
    private ActivityPreferentialSettlement activityPreferentialSettlement;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private BigDecimalUtils bigDecimalUtils;

    // @Qualifier("com.zhuangjie.shop.feign.OrderFeignService")
    @Autowired
    private OrderFeignService orderFeignService;

    @Autowired
    private ShopCollectService shopCollectService;

    /**
     * 入驻申请
     *
     * @param settleInDTO 以dto结算
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void settleIn(SettleInDTO settleInDTO) throws Exception {
        // 验证-验证码
        phoneCodeRedisService.checkCode(VerificationCodeScenes.SHOP_SETTLE_IN, settleInDTO.getPhone(), settleInDTO.getPhoneCode());
        // 设置一些默认值
        Shop shop = PropertiesCopy.type(SettleInDTO.class, Shop.class).produce(settleInDTO);
        shop.setAccountStatus(ShopStatus.WAIT_VERIFY.getValue());
        shop.setBusinessType(BusinessTypeEnum.SHOP_EAT.getValue());
        shop.setMinimumDeliveryAmount(ShopConstant.DEFAULT_MINIMUM_DELIVERY_AMOUNT);
        shop.setPackingFee(ShopConstant.DEFAULT_PACKING_FEE);
        shop.setBusinessScope(ShopConstant.DEFAULT_BUSINESS_SCOPE);

        // 将数据插入到shop表中
        this.baseMapper.insert(shop);
        // 获取插入数据库中得到的id
        if (Objects.isNull(shop.getId())) {
            throw new RuntimeException("插入数据失败！");
        }
        ShopInfo shopInfo = PropertiesCopy.type(SettleInDTO.class, ShopInfo.class).produce(settleInDTO);
        shopInfo.setShopId(shop.getId());
        shopInfoService.save(shopInfo);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void approveResidencyApplication(Long shopId) {
        // 改变shop的状态
        LambdaUpdateWrapper<Shop> updateShopStateWrapper = Wrappers.lambdaUpdate(Shop.class)
                .eq(Shop::getId, shopId)
                .set(Shop::getAccountStatus, ShopStatus.NORMAL.getValue());
        if (!this.update(updateShopStateWrapper)) {
            throw new RuntimeException("修改店铺状态失败");
        }
        // 查询店铺信息
        Shop shop = this.baseMapper.selectById(shopId);
        // 创建店主账号
        MerchantAccount shopOwnerAccount = merchantAccountService.createShopOwnerAccount(shop);

        // 以短信的方式将店主账号发给店主
        // TODO 这里应以异步的方式发送并且当失败时使用MQ补尝
        log.info("店铺店主账号已创建，账号：{}, 密码：{}", shopOwnerAccount.getUsername(), shopOwnerAccount.getPassword());

    }

    @Override
    public Integer getShopBusinessStatus(Long shopId) throws Exception {
        if (shopId == null) {
            // 如果没有传入shopId，身份应该是店铺的请求，否则报错
            userRequestInterceptor.tryMountCurrentMountLoginState();
            if ((shopId = ShopModuleTokenDataProvider.getShopId()) == null) {
                throw new RuntimeException("无法确定获取哪个店铺的状态！");
            }
        }
        LambdaQueryWrapper<Shop> confirmShopStatusWrapper = Wrappers.lambdaQuery(Shop.class)
                .eq(Shop::getId, shopId)
                .eq(Shop::getBusinessStatus, ShopBusinessStatus.OPEN);
        return this.count(confirmShopStatusWrapper) > 0 ? ShopBusinessStatus.OPEN : ShopBusinessStatus.CLOSE;
    }

    /**
     * 店铺信息元素的声明式收集
     *
     * @param shopInfoList                               店铺列表
     * @param shopInfoElementEnumAndInjectTargetFieldMap 商店信息元素枚举和注入目标字段Map
     * @see ShopInfoElementEnum
     */
    @Override
    public <T> void shopInfoElementCollect(List<T> shopInfoList, Map<ShopInfoElementEnum, LambdaStructureParser.NAHRFunction<T, Object>> shopInfoElementEnumAndInjectTargetFieldMap) {
        // “填充快查” Map的key属性
        String keyFieldName = LambdaStructureParser.convertToFieldName(Shop::getId);
        // “填充快查” Map
        Map<Long, T> shopIdAndNearbyShopMap = shopInfoList.stream().collect(Collectors.toMap(nearbyShop -> (Long) ReflectionUtils.getFieldValue(nearbyShop, keyFieldName), nearbyShop -> nearbyShop));

        CompletableFuture.allOf(
                // 填充：店铺优先展示菜品列表
                CompletableFuture.runAsync(() -> {
                    if (!shopInfoElementEnumAndInjectTargetFieldMap.containsKey(ShopInfoElementEnum.BETTER_DISPLAY_DISH)) {
                        return;
                    }
                    // 是上面店铺的优先显示菜品
                    List<Dish> dishList = dishService.list(Wrappers.lambdaQuery(Dish.class)
                            .in(Dish::getShopId, shopIdAndNearbyShopMap.keySet())
                            .eq(Dish::getStatus, DishStatus.START_SALE.getValue())
                            .eq(Dish::getBetterDisplay, DishBetterDisplayStatus.OPEN.getValue()));
                    // 分配给店铺
                    Map<Long, List<Dish>> shopDishList = dishList.stream().sorted(Comparator.comparing(Dish::getUpdateTime)).collect(Collectors.groupingBy(Dish::getShopId));
                    shopIdAndNearbyShopMap.forEach((currentShopId, currentShopInfo) -> {
                        List<Dish> dishes = Optional.ofNullable(shopDishList.get(currentShopId)).orElse(Collections.emptyList());
                        String fieldName = LambdaStructureParser.convertToFieldName(shopInfoElementEnumAndInjectTargetFieldMap.get(ShopInfoElementEnum.BETTER_DISPLAY_DISH));
                        ReflectionUtils.setFieldValue(currentShopInfo, fieldName, dishes);
                    });
                }, contextAwareExecutor),
                // 填充：店铺加预定标识时间
                CompletableFuture.runAsync(() -> {
                    if (!shopInfoElementEnumAndInjectTargetFieldMap.containsKey(ShopInfoElementEnum.PREDETERMINE_FLAG)) {
                        return;
                    }
                    String fieldName = LambdaStructureParser.convertToFieldName(shopInfoElementEnumAndInjectTargetFieldMap.get(ShopInfoElementEnum.PREDETERMINE_FLAG));
                    Map<Long, String> shopIdAndBookableFlagMap = selectEarliestBookingTime(new ArrayList<>(shopIdAndNearbyShopMap.keySet()));
                    shopInfoList.forEach(nearbyShop -> {
                        Object keyFieldValue = ReflectionUtils.getFieldValue(nearbyShop, keyFieldName);
                        if (keyFieldValue instanceof Long) {
                            ReflectionUtils.setFieldValue(nearbyShop, fieldName, shopIdAndBookableFlagMap.get(keyFieldValue));
                        }
                    });
                }, contextAwareExecutor),
                // 填充：统计信息
                CompletableFuture.runAsync(() -> {
                    try {
                        if (!shopInfoElementEnumAndInjectTargetFieldMap.containsKey(ShopInfoElementEnum.STATISTICAL_INFO)) {
                            return;
                        }
                        // 店铺评分与销量信息
                        CompletableFuture<Void> shopEvaluateFuture = CompletableFuture.runAsync(() -> {
                            // 查询最近30天的评分及其数量
                            LambdaQueryWrapperPlusHelper
                                    .forGroupUse(new LambdaQueryWrapperPlus<>(ShopEvaluate.class)
                                            .in(ShopEvaluate::getShopId, shopIdAndNearbyShopMap.keySet())
                                            .gt(ShopEvaluate::getCreateTime, LocalDateTime.now().minusDays(30))
                                            .groupBy(ShopEvaluate::getShopId), ShopStatisticalForShopShow.class)
                                    .use(AggregationOperation.AVG, ShopEvaluate::getScore, value -> value.to(ShopStatisticalForShopShow::getScore))
                                    .use(AggregationOperation.COUNT, ShopEvaluate::getScore, (value) -> value.to(ShopStatisticalForShopShow::getNumberOfRatings))
                                    .inject(keyMap -> {
                                        T t = shopIdAndNearbyShopMap.get(keyMap.get(ShopEvaluate::getShopId));
                                        if (t != null) {
                                            ShopStatisticalForShopShow statistical = ReflectionUtils.getFieldValue(t, ShopStatisticalForShopShow.class);
                                            if (statistical == null) {
                                                ReflectionUtils.setFieldValue(t, ShopStatisticalForShopShow.class, statistical = new ShopStatisticalForShopShow());
                                            }
                                            return statistical;
                                        }
                                        return null;
                                    });
                        }, contextAwareExecutor);
                        String shopIds = shopIdAndNearbyShopMap.keySet().stream().map(String::valueOf).collect(Collectors.joining(","));
                        Map<Long, Long> shopIdAndMonthSalesCountMap = TypeR.extract(orderFeignService.batchGetShopMonthSalesCount(shopIds, 30));
                        // 等待本地的另一个评价信息填充完成，因为如果上面的已经填充这里也填充了，就会出现覆盖
                        shopEvaluateFuture.join();
                        shopIdAndMonthSalesCountMap.forEach((key, value) -> {
                            T nearbyShopVO = shopIdAndNearbyShopMap.get(key);
                            if (nearbyShopVO == null) return;
                            ShopStatisticalForShopShow statistical = ReflectionUtils.getFieldValue(nearbyShopVO, ShopStatisticalForShopShow.class);
                            if (statistical == null) {
                                ReflectionUtils.setFieldValue(nearbyShopVO, ShopStatisticalForShopShow.class, statistical = new ShopStatisticalForShopShow());
                            }
                            statistical.setMonthlySoldQuantity(value);
                        });
                    } catch (Exception e) {
                        throw new RuntimeException("查询shops近30天月售数量失败！" + e.getMessage());
                    }
                }, contextAwareExecutor),
                // 填充：店铺活动信息
                CompletableFuture.runAsync(() -> {
                    if (!shopInfoElementEnumAndInjectTargetFieldMap.containsKey(ShopInfoElementEnum.ACTIVITY_INFO)) {
                        return;
                    }
                    // 暂-只查询全额立减活动
                    Map<Long, Map<Long, ShopActivityInstance>> shopActivityTree = queryShopActivityTreeByShopIdsAndActivityTypeIds(shopIdAndNearbyShopMap.keySet(), Collections.singletonList(ActivityType.FULL_REDUCTION.getCode()));
                    // 店铺id与不同类型的活动实例集合
                    Map<Long,List<ShopActivityInstance>> shopActivityListMap = shopActivityTree.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue().values())));
                    // 排序 TODO
                    // 填充
                    for (T nearbyShop : shopInfoList) {
                        Object keyFieldValue = ReflectionUtils.getFieldValue(nearbyShop, keyFieldName);
                        if (!(keyFieldValue instanceof Long)) continue;
                        String fieldName = LambdaStructureParser.convertToFieldName(shopInfoElementEnumAndInjectTargetFieldMap.get(ShopInfoElementEnum.ACTIVITY_INFO));
                        List<ShopActivityInstance> shopActivityInstances = Optional.ofNullable(shopActivityListMap.get(keyFieldValue)).orElse(new ArrayList<>());
                        List<String[]> activityInstanceTitleArrays = shopActivityInstances.stream().map(activityInstance -> Optional.ofNullable(activityInstance.getTitle()).orElse("").split(ActivityConstant.INSTANCE_TITLE_GRADIENT_SEPARATOR)).collect(Collectors.toList());
                        ReflectionUtils.setFieldValue(nearbyShop,fieldName,activityInstanceTitleArrays);
                    }
                })
        ).join();
    }

    @Override
    public Page<NearbyShopVO> searchNearbyStores(SearchNearbyStoresPageDTO searchNearbyStoresPageDTO) {
        Page<NearbyShopVO> page = new Page<>(searchNearbyStoresPageDTO.getCurrentPage(), searchNearbyStoresPageDTO.getPageSize());
        Page<NearbyShopVO> nearbyShopPage = this.baseMapper.searchNearbyStoresBaseInfo(page, searchNearbyStoresPageDTO);

        List<NearbyShopVO> nearbyShopList = nearbyShopPage.getRecords();
        if (nearbyShopList == null || nearbyShopList.isEmpty()) {
            return nearbyShopPage;
        }

        // 声明需要注入的店铺数据
        Map<ShopInfoElementEnum, LambdaStructureParser.NAHRFunction<NearbyShopVO, Object>> shopInfoElementEnum = new HashMap<>();
        shopInfoElementEnum.put(ShopInfoElementEnum.BETTER_DISPLAY_DISH, NearbyShopVO::getDishList);
        shopInfoElementEnum.put(ShopInfoElementEnum.PREDETERMINE_FLAG, NearbyShopVO::getBookable);
        shopInfoElementEnum.put(ShopInfoElementEnum.STATISTICAL_INFO, NearbyShopVO::getStatistical);

        // 收集
        shopInfoElementCollect(nearbyShopList, shopInfoElementEnum);

        return nearbyShopPage;
    }


    @Override
    public ShopInfoVO getShopInfoById(Long shopId) throws ExecutionException, InterruptedException, InterruptedException {
        // 异步查询店铺信息
        CompletableFuture<Shop> shopCompletableFuture = CompletableFuture.supplyAsync(() -> this.baseMapper.selectById(shopId));
        // 异步获取店铺分类-菜品树
        CompletableFuture<List<DishCategory>> dishCategoryTreeCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return dishCategoryService.getShopDishCategory(shopId, true);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        });
        // 是否关注了该店铺
        CompletableFuture<Boolean> isFollowCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                // 当获取不到用户id会报错
                UserProvider.currentUserId();
                return shopCollectService.isFollow(shopId);
            } catch (Exception e) {
                return null;
            }
        }, contextAwareExecutor);
        // 等待所有异步任务完成
        CompletableFuture.allOf(shopCompletableFuture, dishCategoryTreeCompletableFuture, isFollowCompletableFuture).join();
        // 开始组装结果集
        Shop shop = shopCompletableFuture.get();
        List<DishCategory> dishCategoryTree = dishCategoryTreeCompletableFuture.get();
        Boolean isFollowShop = isFollowCompletableFuture.get();
        if (shop == null) {
            throw new RuntimeException("获取不到店铺数据！");
        }
        // 将店铺的信息转为要给前端的信息（ 店铺一些信息 -> result）
        ShopInfoVO shopInfoVO = PropertiesCopy.type(Shop.class, ShopInfoVO.class).produce(shop);
        // 将菜品分类（->菜品）信息放到shopInfoVO下 （分类菜品树 -> result）
        shopInfoVO.setDishCategoryList(dishCategoryTree);
        // 是否关注了该店铺放到shopInfoVO下
        shopInfoVO.setIsFollow(isFollowShop);

        return shopInfoVO;
    }


    /**
     * 获取店铺业务信息
     *
     * @param shopId 店铺id
     * @return {@link ShopPredetermineInfo}
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @Override
    public ShopPredetermineInfo getShopBusinessInfo(Long shopId) throws ExecutionException, InterruptedException {
        // 获取店铺关于配送的相关数据
        CompletableFuture<ShopPredetermineInfo> shopDeliveryInfoFuture = CompletableFuture.supplyAsync(() -> {
            ShopPredetermineInfo ShopPredetermineInfo = this.baseMapper.selectShopBusinessInfo(shopId);
            if (ShopPredetermineInfo == null) throw new RuntimeException("无法获取店铺配送相关信息！");
            return ShopPredetermineInfo;
        });
        // 获取店铺营业时间段
        CompletableFuture<ShopPredetermineInfo> shopPredetermineDateValidPeriodFuture = CompletableFuture.supplyAsync(() -> {
            // 1、请求店铺的规则
            List<ShopBusinessRules> shopBusinessRules = shopBusinessRulesService.getShopBusinessRules(shopId);
            // 2、获取店铺允许预约天数
            ShopPredetermineInfo shopPredetermineInfo = null;
            try {
                // 需要获取店铺允许预约天数
                shopPredetermineInfo = shopDeliveryInfoFuture.get();
            } catch (Exception e) {
                throw new RuntimeException("获取店铺预约信息失败！无法生成shopPredetermineDateValidPeriod .");
            }
            // 3、生成可预定的天数的可预约时间段
            List<List<TimeRange>> shopPredetermineDateValidPeriod = genPredetermineDaysValidPeriod(shopPredetermineInfo.getAdvanceBookingDays(), shopBusinessRules);
            // 4、组装
            shopPredetermineInfo.setPredetermineDaysValidPeriod(shopPredetermineDateValidPeriod);
            return shopPredetermineInfo;
        });
        // 将组装好的数据返回
        return shopPredetermineDateValidPeriodFuture.get();
    }

    /**
     * 生成有效天数的预定时间段
     * [[],[],...]
     *
     * @param advanceBookingDays 提前预订天数
     * @param shopBusinessRules  商店商业规则
     * @return {@link List}<{@link List}<{@link TimeRange}>>
     */
    public List<List<TimeRange>> genPredetermineDaysValidPeriod(Integer advanceBookingDays, List<ShopBusinessRules> shopBusinessRules) {
        // - 将店铺规则解析出：星期（1-7）与有效预约时间段
        Map<Integer, List<TimeRange>> shopWeekPredetermineValidPeriod = getShopWeekPredetermineValidPeriod(shopBusinessRules);
        // - 将店铺规则解析出：指定日期与有效预约时间段
        Map<LocalDateRange, List<TimeRange>> shopDatePredetermineValidPeriod = getShopDatePredetermineValidPeriod(shopBusinessRules);
        // 由输入参数得到预约时间如3天，那就是i=0今天的预定时间段，i=1明天的预定时间段，i=2后天的预定时间段
        List<List<TimeRange>> shopPredetermineDateValidPeriod = processedToShopPredetermineDaysValidPeriod(advanceBookingDays, shopWeekPredetermineValidPeriod, shopDatePredetermineValidPeriod);
        return shopPredetermineDateValidPeriod;
    }


    @Override
    public ShopToUserDeliveryInfo getShopDeliveryInfoByUserGeoCoordinates(Long shopId, GeoCoordinates geoCoordinates) {
        return this.baseMapper.selectShopDeliveryInfoByUserGeoCoordinates(shopId, geoCoordinates);
    }


    @Override
    public GeneratedPredetermineDayAndTimesInfo getShopBusinessDaysTimes(Long shopId, String receivingMethod, Long addressBookId) throws Exception {
        // 校验参数合法性
        if (BusinessTypeEnum.DELIVERY.getValue().equals(receivingMethod) && addressBookId == null)
            throw new RuntimeException("是配送方式，但未传入用户配送地址id");
        // 获取店铺有效可预订天数及其有效时间段
        ShopPredetermineInfo shopBusinessInfo = getShopBusinessInfo(shopId);
        // 基于shopBusinessInfo计算出daysTimes
        Integer deliveryTime = 0;
        if (BusinessTypeEnum.DELIVERY.getValue().equals(receivingMethod)) {
            ShopToUserDeliveryInfo shopToUserDeliveryInfo = this.getShopDeliveryInfoByUserAddressBookId(shopId, addressBookId);
            deliveryTime = shopToUserDeliveryInfo.getDeliveryTime();
        }
        return PredetermineDatesAndTimeRangesInfoGenerator.gen(shopBusinessInfo.getPredetermineDaysValidPeriod(), shopBusinessInfo.getMakeTime(), deliveryTime);
    }

    @Override
    public PredetermineDayAndTimeRangesForInternalUse getShopBusinessDaysTimeRangesForInternalUse(Long shopId, String receivingMethod, Long addressBookId) throws Exception {
        // 校验参数合法性
        if (BusinessTypeEnum.DELIVERY.getValue().equals(receivingMethod) && addressBookId == null)
            throw new RuntimeException("是配送方式，但未传入用户配送地址id");
        // 获取店铺有效可预订天数及其有效时间段
        ShopPredetermineInfo shopBusinessInfo = getShopBusinessInfo(shopId);
        // 封装返回对象
        PredetermineDayAndTimeRangesForInternalUse result = new PredetermineDayAndTimeRangesForInternalUse();
        // 基于shopBusinessInfo计算出daysTimes
        Integer deliveryTime = 0;
        if (BusinessTypeEnum.DELIVERY.getValue().equals(receivingMethod)) {
            ShopToUserDeliveryInfo shopToUserDeliveryInfo = this.getShopDeliveryInfoByUserAddressBookId(shopId, addressBookId);
            result.setDeliveryTime(deliveryTime = shopToUserDeliveryInfo.getDeliveryTime());
        }
        // 生成含尽快送达时间
        LocalDateTime immediateDeliveryTime = PredetermineDatesAndTimeRangesInfoGenerator.getImmediateDeliveryTime(shopBusinessInfo.getPredetermineDaysValidPeriod(), shopBusinessInfo.getMakeTime(), deliveryTime);
        result.setImmediatePredetermineDateTime(immediateDeliveryTime);
        result.setPredetermineDaysValidPeriod(shopBusinessInfo.getPredetermineDaysValidPeriod());
        result.setMakeTime(shopBusinessInfo.getMakeTime());
        return result;
    }

    @Override
    public CreateOrderShopBasicInfo getCreateOrderBasicInfo(Long shopId) {
        return this.baseMapper.getCreateOrderBasicInfo(shopId);
    }

    @Override
    public String getShopPhone(Long shopId) {
        String phone = this.baseMapper.getShopPhone(shopId);
        if (phone == null) throw new RuntimeException("查询了一个不存在的店铺");
        return phone;
    }

    @Override
    public List<ShopInfoForOrder> getShopInfoListForOrder(List<String> ids) {
        List<Shop> shops = this.baseMapper.selectBatchIds(ids);
        // 批量将Shop对象转为ShopInfoForOrder对象
        List<ShopInfoForOrder> shopInfoForOrders = PropertiesCopy.type(Shop.class, ShopInfoForOrder.class)
                .config(c -> c.map(Shop::getId, ShopInfoForOrder::getShopId).map(Shop::getName, ShopInfoForOrder::getShopName).map(Shop::getLogo, ShopInfoForOrder::getShopLogo))
                .batchProduce(shops);
        return shopInfoForOrders;
    }

    /**
     * 选择最早预订时间
     *
     * @param shopIds 商店ID
     * @return {@link Map}<{@link Long}, {@link String}>
     */
    @Override
    public Map<Long, String> selectEarliestBookingTime(List<Long> shopIds) {
        if (shopIds == null || shopIds.size() == 0) {
            throw new RuntimeException("在查询店铺是否营业时，传入的ids无效");
        }
        // 查出这些店铺允许预定天数
        Map<Long, Integer> shopIdAndAdvanceBookingDaysMap = this.baseMapper.selectAdvanceBookingDaysByShopIds(shopIds).stream().collect(Collectors.toMap(ShopAdvanceBookingDays::getShopId, ShopAdvanceBookingDays::getAdvanceBookingDays));
        // 查询这些店铺的经营规则
        LambdaQueryWrapper<ShopBusinessRules> queryShopsBusinessRules = Wrappers.lambdaQuery(ShopBusinessRules.class)
                .in(ShopBusinessRules::getShopId, shopIds);
        List<ShopBusinessRules> shopBusinessRules = shopBusinessRulesService.list(queryShopsBusinessRules);
        // 分开得到若干 shopId->List<ShopBusinessRules>
        Map<Long, List<ShopBusinessRules>> shopIdAndBusinessRulesMap = new HashMap<>();
        for (ShopBusinessRules shopBusinessRule : shopBusinessRules) {
            List<ShopBusinessRules> currentShopBusinessRules = shopIdAndBusinessRulesMap.get(shopBusinessRule.getShopId());
            if (currentShopBusinessRules == null)
                shopIdAndBusinessRulesMap.put(shopBusinessRule.getShopId(), (currentShopBusinessRules = new ArrayList<>()));
            currentShopBusinessRules.add(shopBusinessRule);
        }
        // 店铺id和预定天数的有效时间段Map
        Map<Long, List<List<TimeRange>>> shopIdAndPredetermineDaysValidPeriodMap = shopIds.stream().collect(Collectors.toMap(shopId -> shopId, shopId -> genPredetermineDaysValidPeriod(shopIdAndAdvanceBookingDaysMap.get(shopId), shopIdAndBusinessRulesMap.get(shopId))));
        Map<Long, String> shopIdAndPredetermineTimeMap = new HashMap<>();
        for (Long shopId : shopIdAndPredetermineDaysValidPeriodMap.keySet()) {
            List<List<TimeRange>> bookingTimeSlot = shopIdAndPredetermineDaysValidPeriodMap.get(shopId);
            // 取第一天
            String predetermineTime = "";
            List<TimeRange> timeRanges = bookingTimeSlot.get(0);
            if (timeRanges != null) {
                for (int i = 0; i < timeRanges.size(); i++) {
                    TimeRange timeRange = timeRanges.get(i);
                    if (timeRange.in(LocalTime.now())) {
                        // null表示该店铺当前正处于可下单状态
                        predetermineTime = null;
                        break;
                    } else if (timeRange.inLeft(LocalTime.now())) {
                        predetermineTime = new LocalTimeOperations(timeRange.getStartTime()).format("HH:mm");
                        break;
                    }
                }
            }
            shopIdAndPredetermineTimeMap.put(shopId, predetermineTime);
        }
        return shopIdAndPredetermineTimeMap;
    }

    @Override
    public ShopForRider getShopAddress(Long shopId) {
        return this.baseMapper.getShopAddress(shopId);
    }


    @Override
    public Short queryShopDeliveryMethod(Long shopId) {
        return this.baseMapper.queryShopDeliveryMethod(shopId);
    }

    @Override
    public ShopBasicInfo getShopBasicInfo() {
        Long shopId = ShopModuleTokenDataProvider.getShopId();
//        LambdaQueryWrapper<Shop> queryShopBasicInfoWrapper = Wrappers.lambdaQuery(Shop.class)
//                .eq(Shop::getId, shopId)
//                .select(Arrays.asList(Shop::getId, Shop::getName, Shop::getPhone, Shop::getLogo, Shop::getBackgroundPoster, Shop::getNotice, Shop::getFlags));
        Shop shop = this.getById(shopId);
        // Shop -dump-> ShopBasicInfoVO
        return PropertiesCopy.type(Shop.class, ShopBasicInfo.class).produce(shop);
    }

    @Override
    public void updateShopBasicInfo(ShopBasicInfo shopBasicInfo) {
        Long shopId = ShopModuleTokenDataProvider.getShopId();
        Shop shop = PropertiesCopy.type(ShopBasicInfo.class, Shop.class).produce(shopBasicInfo);
        boolean isUpdateSuccess = this.update(shop, Wrappers.lambdaQuery(Shop.class).eq(Shop::getId, shopId));
        if (!isUpdateSuccess) throw new RuntimeException("更新店铺基本信息失败");
    }

    @Override
    public ShopBusinessConfigVO queryBusinessConfig() {
        Long shopId = ShopModuleTokenDataProvider.getShopId();
        return this.baseMapper.queryBusinessConfig(shopId);
    }

    @Override
    public void updateBusinessConfig(ShopBusinessConfig shopBusinessConfig) {
        // 1. 进行业务逻辑值的检验
        // 1.1. 检查businessType
        String businessTypes = shopBusinessConfig.getBusinessType();
        for (String businessType : businessTypes.split(CommonSeparatorConstants.MULTIPLE_SEPARATOR)) {
            if (!EnumValidator.isValid(businessType, BusinessTypeEnum.class, BusinessTypeEnum::getValue))
                throw new RuntimeException("businessType业务值存在不合法！");
        }
        // 1.2. 检查deliveryMethod
        if (!EnumValidator.isValid(shopBusinessConfig.getDeliveryMethod(), DeliveryMethodEnum.class, DeliveryMethodEnum::getValue)) {
            throw new RuntimeException("deliveryMethod业务值存在不合法！");
        }
        // 2. 进行保存操作
        Long shopId = ShopModuleTokenDataProvider.getShopId();
        Shop shop = PropertiesCopy.type(ShopBusinessConfig.class, Shop.class).produce(shopBusinessConfig);
        boolean isUpdateSuccess = this.update(shop, Wrappers.lambdaQuery(Shop.class).eq(Shop::getId, shopId));
        if (!isUpdateSuccess) {
            throw new RuntimeException("更新店铺营业配置失败");
        }
    }


    @Override
    public ShopPreferentialLabelInfoVO queryPreferentialInfoByShopId(Long shopId) throws Exception {

        // 1. 查询需要数据
        // 1.1 查询店铺活动
        List<String[]> shopActivityLabelList = queryShopActivityInstanceMap(shopId).values().stream().map(shopActivityInstance -> {
            String activityInstanceLabel = shopActivityInstance.getTitle();
            if (activityInstanceLabel.contains(ActivityConstant.INSTANCE_TITLE_GRADIENT_SEPARATOR)) {
                return shopActivityInstance.getTitle().split(ActivityConstant.INSTANCE_TITLE_GRADIENT_SEPARATOR);
            }
            return new String[]{activityInstanceLabel};
        }).collect(Collectors.toList());
        // 1.2 查询店铺优惠券
        CompletableFuture<List<CouponCoreInfo>> queryUserShopCouponFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return TypeR.extract(couponFeignService.queryApplicableShopCoupon(shopId));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, contextAwareExecutor);
        // 1.3 查询拥有的平台优惠券
        CompletableFuture<List<CouponCoreInfo>> queryUserOwnedPlatformCouponFuture = CompletableFuture.supplyAsync(() -> {
            // 店铺是否参与了平台膨胀券活动
            Boolean isParticipatingInPlatformInflationCouponActivity = shopActivityInstanceService.isShopInvolvedInPlatformCouponActivity(shopId);
            try {
                return TypeR.extract(couponFeignService.queryReceivedPlatformCoupon(isParticipatingInPlatformInflationCouponActivity));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, contextAwareExecutor);
        // 等待请求完成
        CompletableFuture.allOf(queryUserShopCouponFuture, queryUserOwnedPlatformCouponFuture);
        // 2. 构建返回值
        return ShopPreferentialLabelInfoVO.builder()
                .activityLabels(shopActivityLabelList)
                .shopCouponList(queryUserShopCouponFuture.get())
                .platformCouponList(queryUserOwnedPlatformCouponFuture.get())
                .build();
    }

    @Override
    public OrderConfirmRequireVO queryOrderConfirmRequire(QueryConfirmRequireDTO queryConfirmRequireDTO) throws ExecutionException, InterruptedException {
        // 1. 查询需要用到的信息
        // 1.1 查询店铺信息
        CompletableFuture<Shop> shopCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return this.getById(queryConfirmRequireDTO.getShopId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, contextAwareExecutor);
        // 1.2 查询最佳的收货地址信息
        CompletableFuture<AddressBookVO> addressBookVOCompletableFuture = CompletableFuture.supplyAsync(() -> {
            // 如果不是配送，则不查询收货地址
            if (!Objects.equals(queryConfirmRequireDTO.getReceivingMethod(), BusinessTypeEnum.DELIVERY.getValue()))
                return null;
            try {
                // 等待上面查询店铺信息的异步完成
                Shop shop = shopCompletableFuture.get();
                // 如果有地址，直接查询指定的地址
                if (queryConfirmRequireDTO.getAddressBookId() != null) {
                    AddressBook addressBook = TypeR.extract(userFeignService.getAddressBookById(queryConfirmRequireDTO.getAddressBookId()));
                    // 判断地址是否合理
                    if (addressBook != null && GeoCoordinateUtils.calculateDistance(addressBook.getLongitude(), addressBook.getLatitude(), shop.getLongitude(), shop.getLatitude()) <= shop.getBusinessScope()) {
                        return PropertiesCopy.type(AddressBook.class, AddressBookVO.class).produce(addressBook);
                    }
                }
                // 如果有地址但不合理或没有地址时使用最佳地址
                return TypeR.extract(userFeignService.preferredAddress(shop.getLongitude(), shop.getLatitude(), shop.getBusinessScope()));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, contextAwareExecutor);
        // 1.3 购物车信息
        CompletableFuture<List<CartItemVO>> shopShoppingCartFuture = CompletableFuture.supplyAsync(() -> shoppingCartService.listUserShoppingCart(queryConfirmRequireDTO.getShopId()), contextAwareExecutor);
        // 1.4 查询出店铺的活动列表
        CompletableFuture<Map<Long, ShopActivityInstance>> shopActivityCompletableFuture = CompletableFuture.supplyAsync(() -> this.queryShopActivityInstanceMap(queryConfirmRequireDTO.getShopId()), contextAwareExecutor);
        // 1.5 查询所领取的与可领取的店铺优惠券 && 获取拥有的平台优惠券
        CompletableFuture<ApplicableShopCouponCoreInfoVO> applicableShopCouponCoreInfoVOCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                return TypeR.extract(couponFeignService.queryApplicableShopCouponCoreInfo(queryConfirmRequireDTO.getShopId(), shopActivityInstanceService.isShopInvolvedInPlatformCouponActivity(queryConfirmRequireDTO.getShopId())));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, contextAwareExecutor);

        // 等待所有请求请求完成
        CompletableFuture.allOf(addressBookVOCompletableFuture, shopShoppingCartFuture, shopActivityCompletableFuture, applicableShopCouponCoreInfoVOCompletableFuture, shopCompletableFuture);
        AddressBookVO addressBookVO = addressBookVOCompletableFuture.get();
        List<CartItemVO> cartItemVOList = shopShoppingCartFuture.get();
        Map<Long, ShopActivityInstance> shopActivityInstanceMap = shopActivityCompletableFuture.get();
        ApplicableShopCouponCoreInfoVO applicableShopCouponCoreInfoVO = applicableShopCouponCoreInfoVOCompletableFuture.get();
        Shop shop = shopCompletableFuture.get();
        // 2. 开始使用上面查询的数据操作
        // 2.1 计算活动优惠的信息
        ActivityEnvRequireMetaInformation metaInformation = ActivityEnvRequireMetaInformation.builder()
                .userId(UserProvider.currentUserId())
                .dishList(cartItemVOList)
                .shopId(queryConfirmRequireDTO.getShopId())
                .receivingMethod(queryConfirmRequireDTO.getReceivingMethod())
                .userGeoCoordinates(addressBookVO == null ? null : new GeoCoordinates(addressBookVO.getLongitude(), addressBookVO.getLatitude()))
                .build();
        // 2.2 看可命中的活动
        ActivityEnv activityEnv = new ActivityEnv(applicationContext, metaInformation);
        // 提供shop后内部无需进行查询了
        activityEnv.setShop(shop);
        // 可用的活动
        List<OrderActivity> activityListVO = activityPreferentialSettlement.settlement(activityEnv, shopActivityInstanceMap.values());
        // [总活动优惠金额,纯菜品优惠金额,优惠的配送费金额]
        BigDecimal[] activityDeductionAmountArr = parseActivityDeductionAmountStructure(activityListVO);
        // 菜品总价+配送费+打包费
        BigDecimal notPreferentialOrderAmount = calculateNotPreferentialOrderAmount(queryConfirmRequireDTO.getReceivingMethod(), activityEnv.getDishListSumPrice(), shop.getDeliveryFee(), shop.getPackingFee());
        // 菜品总价活动后的的金额（菜品总金额-纯菜品优惠的金额）
        BigDecimal dishListActivityDeductionAmount = bigDecimalUtils.subtractionOperation(notPreferentialOrderAmount, activityDeductionAmountArr[0]);
        // 2.3 计算可用优惠券（看是否通过”门槛“与指定”商品限制"）
        // 可用的店铺优惠券
        List<CouponCoreInfo> usableShopCoupons = CouponHelper.couponAvailabilityCheck(cartItemVOList, applicableShopCouponCoreInfoVO.getShopCouponList());
        // 可用的平台优惠券
        List<CouponCoreInfo> usablePlatformCoupons = CouponHelper.couponAvailabilityCheck(cartItemVOList, applicableShopCouponCoreInfoVO.getPlatformCouponList());
        // 抵扣超额也不可用：满减券是对菜品总价格活动后金额来看能不能用的（抵扣的金额不能大于总菜品金额，否则不可用进行过滤）
        removeInvalidFullReductionCoupons(usableShopCoupons, dishListActivityDeductionAmount);
        removeInvalidFullReductionCoupons(usablePlatformCoupons, dishListActivityDeductionAmount);
        // 需要用到里面的：店铺券优惠金额、平台券优惠金额,所有优惠券后价格
        UseCouponDetail useCouponDetail = useCoupons(queryConfirmRequireDTO, applicableShopCouponCoreInfoVO, usableShopCoupons, usablePlatformCoupons, cartItemVOList, dishListActivityDeductionAmount);
        // 聚合券批次
        // 3 结果组装
        OrderConfirmRequireVO orderConfirmRequireVO = new OrderConfirmRequireVO();

        orderConfirmRequireVO.setAddressBook(addressBookVO);
        orderConfirmRequireVO.setCartItemList(cartItemVOList);
        orderConfirmRequireVO.setDishListSumPrice(activityEnv.getDishListSumPrice());
        orderConfirmRequireVO.setDeliveryFee(Objects.equals(queryConfirmRequireDTO.getReceivingMethod(), BusinessTypeEnum.DELIVERY.getValue()) ? shop.getDeliveryFee() : null);
        orderConfirmRequireVO.setPackingFee(!Objects.equals(queryConfirmRequireDTO.getReceivingMethod(), BusinessTypeEnum.SHOP_EAT.getValue()) ? shop.getPackingFee() : null);
        orderConfirmRequireVO.setShopInfo(queryConfirmRequireDTO.isReturnShopInfo() ? shop : null);

        PreferentialInfo preferentialInfo = new PreferentialInfo();
        preferentialInfo.setAvailableActivityList(activityListVO);
        preferentialInfo.setPlatformCouponCount(noStealthCouponCount(usablePlatformCoupons));
        preferentialInfo.setShopCouponCount(usableShopCoupons.size());
        preferentialInfo.setShopCouponBatchIds(usableShopCoupons.stream().map(CouponCoreInfo::getBatchId).distinct().collect(Collectors.toList()));
        preferentialInfo.setPlatformBatchIds(usablePlatformCoupons.stream().map(CouponCoreInfo::getBatchId).distinct().collect(Collectors.toList()));
        preferentialInfo.setActivityDeductionAmount(activityDeductionAmountArr[0]);
        preferentialInfo.setDeliveryFeeDeductionAmount(activityDeductionAmountArr[2]);
        preferentialInfo.setAllShopCouponDeductionAmount(useCouponDetail.getShopCouponPreferentialAmount());
        preferentialInfo.setAllPlatformCouponDeductionAmount(useCouponDetail.getPlatformCouponPreferentialAmount());
        preferentialInfo.setActivityAndCouponPreferentialAfterOrderAmount(useCouponDetail.getCouponPreferentialAfterOrderAmount());
        preferentialInfo.setUsedCoupons(queryConfirmRequireDTO.isReturnCouponDetail() ? buildUseCouponCoreInfoContainer(useCouponDetail) : null);

        orderConfirmRequireVO.setPreferentialInfo(preferentialInfo);
        orderConfirmRequireVO.setSupportedReceivingMethods(activityEnv.getShop().getBusinessType().split(CommonSeparatorConstants.MULTIPLE_SEPARATOR));
        return orderConfirmRequireVO;
    }

    private UseCouponCoreInfoContainer buildUseCouponCoreInfoContainer(UseCouponDetail useCouponDetail) {
        UseCouponCoreInfoContainer useCouponCoreInfoContainer = new UseCouponCoreInfoContainer();
        if (useCouponDetail.getUsePlatformCouponMap() != null && !useCouponDetail.getUsePlatformCouponMap().isEmpty()) {
            useCouponCoreInfoContainer.setUsePlatformCouponPreferentialInfoList(useCouponDetail.getUsePlatformCouponMap().entrySet().stream().map(entry -> {
                CouponCoreInfoAndPreferentialAmount couponCoreInfoAndPreferentialAmount = new CouponCoreInfoAndPreferentialAmount();
                couponCoreInfoAndPreferentialAmount.setCouponCoreInfo(entry.getKey());
                couponCoreInfoAndPreferentialAmount.setPreferentialAmount(entry.getValue());
                return couponCoreInfoAndPreferentialAmount;
            }).collect(Collectors.toList()));
        }
        if (useCouponDetail.getUseShopCouponMap() != null && !useCouponDetail.getUseShopCouponMap().isEmpty()) {
            useCouponCoreInfoContainer.setUseShopCouponPreferentialInfoList(useCouponDetail.getUseShopCouponMap().entrySet().stream().map(entry -> {
                CouponCoreInfoAndPreferentialAmount couponCoreInfoAndPreferentialAmount = new CouponCoreInfoAndPreferentialAmount();
                couponCoreInfoAndPreferentialAmount.setCouponCoreInfo(entry.getKey());
                couponCoreInfoAndPreferentialAmount.setPreferentialAmount(entry.getValue());
                return couponCoreInfoAndPreferentialAmount;
            }).collect(Collectors.toList()));
        }
        return useCouponCoreInfoContainer;
    }

    /**
     * 计算不进行任何优惠前的订单金额
     *
     * @param receivingMethod
     * @param dishListSumPrice
     * @param deliveryFee
     * @param packingFee
     * @return
     */
    private BigDecimal calculateNotPreferentialOrderAmount(String receivingMethod, BigDecimal dishListSumPrice, BigDecimal deliveryFee, BigDecimal packingFee) {
        BigDecimal result = dishListSumPrice;
        if (Objects.equals(receivingMethod, BusinessTypeEnum.DELIVERY.getValue()) || Objects.equals(receivingMethod, BusinessTypeEnum.SELF_PICKUP.getValue())) {
            result = bigDecimalUtils.additionOperation(result, packingFee);
        }
        // 如果是配送单，需要添加配送费
        if (Objects.equals(receivingMethod, BusinessTypeEnum.DELIVERY.getValue())) {
            result = bigDecimalUtils.additionOperation(result, deliveryFee);
        }
        return result;

    }

    /**
     * 使用优惠券
     *
     * @param queryConfirmRequireDTO
     * @param applicableShopCouponCoreInfoVO
     * @param usableShopCoupons
     * @param usablePlatformCoupons
     * @param cartItemVOList
     * @param dishListActivityDeductionAmount
     * @return
     */
    private UseCouponDetail useCoupons(QueryConfirmRequireDTO queryConfirmRequireDTO, ApplicableShopCouponCoreInfoVO applicableShopCouponCoreInfoVO, List<CouponCoreInfo> usableShopCoupons, List<CouponCoreInfo> usablePlatformCoupons, List<CartItemVO> cartItemVOList, BigDecimal dishListActivityDeductionAmount) {
        List<Long> shopCouponIds = queryConfirmRequireDTO.getShopCouponIds();
        List<Long> platformCouponBatchIds = queryConfirmRequireDTO.getPlatformCouponIds();
        if ((shopCouponIds == null || shopCouponIds.isEmpty()) && (platformCouponBatchIds == null || platformCouponBatchIds.isEmpty())) {
            UseCouponDetail useCouponDetail = new UseCouponDetail();
            // 设置优惠后需要支付金额（n-0 = n）
            useCouponDetail.setCouponPreferentialAfterOrderAmount(dishListActivityDeductionAmount);
            return useCouponDetail;
        }
        Map<Long, CouponCoreInfo> allCouponIdMap = applicableShopCouponCoreInfoVO.getShopCouponList().stream().filter(coupon -> coupon.getId() != null).collect(Collectors.toMap(CouponCoreInfo::getId, coupon -> coupon));
        applicableShopCouponCoreInfoVO.getPlatformCouponList().forEach(coupon -> allCouponIdMap.put(coupon.getId(), coupon));
        // 将所有能用于本次订单的券和为一个Map
        Map<Long, CouponCoreInfo> usableCouponBatchIdMap = usableShopCoupons.stream()
                .collect(Collectors.toMap(
                        CouponCoreInfo::getBatchId,
                        coupon -> coupon,
                        // 当键值重复时，保留现有对象
                        (existing, replacement) -> existing
                ));
        usablePlatformCoupons.forEach(coupon -> usableCouponBatchIdMap.put(coupon.getBatchId(), coupon));
        return bestPreferentialCoupon(cartItemVOList, dishListActivityDeductionAmount, queryConfirmRequireDTO, allCouponIdMap, usableCouponBatchIdMap);
    }

    @Override
    public List<ShopDisplayInfo> queryShopsDisplayInfo(List<Long> shopIds) {
        List<ShopDisplayInfo> shopDisplayInfos = this.baseMapper.queryShopsDisplayInfoByIds(shopIds);
        return shopDisplayInfos == null ? new ArrayList<>() : shopDisplayInfos;
    }


    /**
     * 根据传入使用的优惠券，计算得到最终的优惠后的金额
     * 返回 [店铺券优惠金额,平台券优惠金额,所有优惠券后价格]
     *
     * @param cartItemVOList
     * @param dishListActivityDeductionAmount
     * @param queryConfirmRequireDTO
     * @param usableCouponMap
     * @return
     */
    public UseCouponDetail bestPreferentialCoupon(List<CartItemVO> cartItemVOList, BigDecimal dishListActivityDeductionAmount, QueryConfirmRequireDTO queryConfirmRequireDTO, Map<Long, CouponCoreInfo> allCouponIdMap, Map<Long, CouponCoreInfo> usableCouponMap) {
        UseCouponDetail result = new UseCouponDetail();
        Map<Long, Integer> discountedDishIdCountMap = new HashMap<>();
        List<Long>[] couponCollects = new List[]{queryConfirmRequireDTO.getPlatformCouponIds(), queryConfirmRequireDTO.getShopCouponIds()};
        if (!queryConfirmRequireDTO.getIsPlatformCouponFirst()) ArrayUtils.reverse(couponCollects);
        for (List<Long> couponCollect : couponCollects) {
            if (couponCollect == null || couponCollect.isEmpty()) continue;
            for (Long useCouponId : couponCollect) {
                CouponCoreInfo couponCoreInfo = allCouponIdMap.get(useCouponId);
                if (couponCoreInfo == null) {
                    throw new RuntimeException("无法找到使用的券信息");
                }
                // 看该券是否在可使用的券内
                if (usableCouponMap.get(couponCoreInfo.getBatchId()) == null) {
                    throw new RuntimeException("使用的券不在可使用的券内！");
                }
                // 本优惠券优惠的金额
                BigDecimal currentCouponPreferentialAmount = null;

                if (Objects.equals(couponCoreInfo.getType(), CouponType.FULL_REDUCTION.getCode())) {
                    // 如果是满减券，只要 <= 可优惠值即可
                    if (dishListActivityDeductionAmount.compareTo(couponCoreInfo.getParValue()) < 0)
                        throw new RuntimeException("传入的优惠券无法使用，剩余可优惠金额不够减免！");
                    // 满减券可用
                    dishListActivityDeductionAmount = bigDecimalUtils.subtractionOperation(dishListActivityDeductionAmount, (currentCouponPreferentialAmount = bigDecimalUtils.additionOperation(couponCoreInfo.getParValue(), couponCoreInfo.getExpansionAmount())));
                } else if (Objects.equals(couponCoreInfo.getType(), CouponType.DISCOUNT.getCode())) {
                    // 是折扣券
                    // 找到最优惠的菜品进行折扣
                    BigDecimal mostExpensiveItem = null;
                    CartItemVO mostExpensiveCartItemVO = null;
                    for (CartItemVO cartItemVO : CouponHelper.filterApplicableDishList(couponCoreInfo, cartItemVOList)) {
                        Integer currentDiscountedDishCount = discountedDishIdCountMap.get(cartItemVO.getId());
                        if (currentDiscountedDishCount != null && currentDiscountedDishCount >= cartItemVO.getCount())
                            continue;
                        if (mostExpensiveItem == null || mostExpensiveItem.compareTo(cartItemVO.getNowPrice()) < 0) {
                            mostExpensiveItem = cartItemVO.getNowPrice();
                            mostExpensiveCartItemVO = cartItemVO;
                        }
                    }
                    if (mostExpensiveCartItemVO == null) {
                        throw new RuntimeException("商品折扣优惠券无法找到折扣目标菜品");
                    }
                    // 计算能优惠的金额（nowPrice - (nowPrice*折扣率)）
                    currentCouponPreferentialAmount = bigDecimalUtils.subtractionOperation(mostExpensiveCartItemVO.getNowPrice(), bigDecimalUtils.multiplicationOperation(mostExpensiveCartItemVO.getNowPrice(), couponCoreInfo.ifDiscountCouponParValue()));
                    // 计算本券优惠后的价格
                    dishListActivityDeductionAmount = bigDecimalUtils.subtractionOperation(dishListActivityDeductionAmount, currentCouponPreferentialAmount);
                    // 记录菜品已折扣+1
                    Integer currentDiscountedDishCount = discountedDishIdCountMap.get(mostExpensiveCartItemVO.getId());
                    discountedDishIdCountMap.put(mostExpensiveCartItemVO.getId(), currentDiscountedDishCount == null ? 1 : currentDiscountedDishCount + 1);
                }
                // 需要合计店铺券/平台券优惠了多少
                if (Objects.equals(couponCoreInfo.getIssueType(), IssuedType.SHOP_COUPON.getCode())) {
                    if (!couponCoreInfo.getIsStackable() && result.getShopCouponPreferentialAmount() != null) {
                        throw new RuntimeException("选择了多张不可叠加券使用，前端bug!");
                    }
                    result.plusShopCouponPreferentialAmount(currentCouponPreferentialAmount);
                    result.putShopCouponToMap(couponCoreInfo, currentCouponPreferentialAmount);
                } else if (Objects.equals(couponCoreInfo.getIssueType(), IssuedType.PLATFORM_COUPON.getCode())) {
                    if (!couponCoreInfo.getIsStackable() && result.getPlatformCouponPreferentialAmount() != null) {
                        throw new RuntimeException("选择了多张不可叠加券使用，前端bug!");
                    }
                    result.plusPlatformCouponPreferentialAmount(currentCouponPreferentialAmount);
                    result.putPlatformCouponToMap(couponCoreInfo, currentCouponPreferentialAmount);
                }
            }
        }
        result.setCouponPreferentialAfterOrderAmount(dishListActivityDeductionAmount);
        return result;
    }


    /**
     * 从优惠券列表中获取不隐藏（隐藏的是没有但可用的优惠券）的优惠券数量
     *
     * @param usableCoupons
     * @return
     */
    private Integer noStealthCouponCount(List<CouponCoreInfo> usableCoupons) {
        if (usableCoupons == null) return 0;
        return Math.toIntExact(usableCoupons.stream().filter(couponCoreInfo -> couponCoreInfo.getId() != null).count());
    }

    /**
     * 解析活动扣减金额结构
     * [总活动优惠金额,纯菜品优惠金额,优惠的配送费金额]
     *
     * @param activityListVO 活动列表vo
     * @return {@link BigDecimal[]}
     */
    private BigDecimal[] parseActivityDeductionAmountStructure(List<OrderActivity> activityListVO) {
        BigDecimal[] result = new BigDecimal[3];
        for (OrderActivity orderActivity : activityListVO) {
            if (Objects.equals(orderActivity.getActivityId(), ActivityType.DELIVERY_FEE_DISCOUNT.getCode())) {
                // 追加配送费
                result[2] = bigDecimalUtils.additionOperation(result[2], orderActivity.getDeductionAmount());
            } else {
                // 追加纯菜品金额
                result[1] = bigDecimalUtils.additionOperation(result[1], orderActivity.getDeductionAmount());
            }
        }
        // 计算总和
        result[0] = bigDecimalUtils.additionOperation(result[1], result[2]);
        return result;
    }

//    private BigDecimal calculatePromotionDiscountDeliveryFee(List<OrderActivity> activityListVO) {
//
//        return null;
//    }

    /**
     * 计算折扣配送费
     *
     * @param activityListVO 活动列表vo
     * @return {@link BigDecimal}
     */
    private BigDecimal calculateDiscountedDeliveryFee(List<OrderActivity> activityListVO) {
        for (OrderActivity orderActivity : activityListVO) {
            if (Objects.equals(orderActivity.getActivityId(), ActivityType.DELIVERY_FEE_DISCOUNT.getCode())) {
                return orderActivity.getDeductionAmount();
            }
        }
        return null;
    }

    /**
     * 移除满减券中抵扣金额大于付款必要金额的券
     *
     * @param usableShopCoupons     店铺可用优惠券列表
     * @param paymentRequiredAmount 必要付款金额
     */
    public void removeInvalidFullReductionCoupons(List<CouponCoreInfo> usableShopCoupons, BigDecimal paymentRequiredAmount) {
        usableShopCoupons.removeIf(couponCoreInfo -> {
            // 如果是满减券，判断抵扣的金额是否大于paymentRequiredAmount
            if (Objects.equals(couponCoreInfo.getType(), CouponType.FULL_REDUCTION.getValue())) {
                return couponCoreInfo.getParValue().compareTo(paymentRequiredAmount) > 0;
            }
            // 其他类型的券不做处理
            return false;
        });
    }

    /**
     * 按店铺id和活动类型id查询店铺活动实例映射
     *
     * @return {@link Map }<{@link Long },{@link Map }<{@link Long }, {@link ShopActivityInstance }>>
     */
    private Map<Long,Map<Long, ShopActivityInstance>> queryShopActivityTreeByShopIdsAndActivityTypeIds(Collection<Long> shopIds, Collection<Long> activityTypeIds) {
        if (CollectionUtils.isEmptyIgnoreNullElement(shopIds)) {
            return Collections.emptyMap();
        }
        Map<Long,Map<Long, ShopActivityInstance>> result = shopIds.stream().collect(Collectors.toMap(item->item,item->new HashMap<>()));
        List<ShopActivityInstance> shopActivities = shopActivityInstanceService.list(Wrappers.lambdaQuery(ShopActivityInstance.class)
                .in(ShopActivityInstance::getShopId, shopIds)
                .in(CollectionUtils.isNotEmpty(activityTypeIds),ShopActivityInstance::getActivityId, activityTypeIds)
                .lt(ShopActivityInstance::getStartDate, LocalDateTime.now())
                .and(wrapper -> wrapper.isNull(ShopActivityInstance::getEndDate).or(wrapper2 -> wrapper2.gt(ShopActivityInstance::getEndDate, LocalDateTime.now())))
                // 这里降序后面冲突覆盖（旧<-新）
                .orderByAsc(ShopActivityInstance::getUpdateTime));
        // 如果没有店铺活动，直接返回空的
        if (shopActivities == null || shopActivities.isEmpty()) {
            return Collections.emptyMap();
        }
        // 查询出来的还要进行周过滤与时段过滤
        shopActivities = shopActivities.stream().filter(shopActivityInstance -> {
            // 看周
            String weeks = shopActivityInstance.getWeeks();
            if (!StringUtils.isBlank(weeks) && !weeks.contains(WeekHelper.dayOfWeek() + "")) {
                return false;
            }
            // 看时间段
            String timeRanges = shopActivityInstance.getTimeRanges();
            if (timeRanges == null) return true;
            return !StringUtils.isBlank(timeRanges) && Arrays.stream(timeRanges.split(CommonSeparatorConstants.MULTIPLE_SEPARATOR))
                    .map(timeRange -> {
                        String[] timeSE = timeRange.split(CommonSeparatorConstants.TIME_RANGE_SEPARATOR);
                        if (timeSE.length != 2) return null;
                        return new TimeRange(timeSE[0], timeSE[1]);
                    })
                    .anyMatch(item -> item != null && item.in(LocalTime.now()));
        }).collect(Collectors.toList());
        // 查询出来-如果冲突，采用最近设置的
        for (ShopActivityInstance shopActivity : shopActivities) {
            Long activityId = shopActivity.getShopId();
            Map<Long, ShopActivityInstance> shopActivityMap = result.computeIfAbsent(activityId, k -> new HashMap<>());
            shopActivityMap.put(shopActivity.getActivityId(), shopActivity);
        }

        return result;
    }

    /**
     * 查询店铺所有的活动实例
     * （每种活动只会查到一个instance）
     *
     * @param shopId
     * @return
     */
    private Map<Long, ShopActivityInstance> queryShopActivityInstanceMap(Long shopId) {
        Map<Long, Map<Long, ShopActivityInstance>> shopActivityTree = queryShopActivityTreeByShopIdsAndActivityTypeIds(Arrays.asList(shopId), null);
        Map<Long, ShopActivityInstance> shopActivityMap = shopActivityTree.get(shopId);
        return Optional.ofNullable(shopActivityMap).orElse(Collections.emptyMap());
    }

    private ShopToUserDeliveryInfo getShopDeliveryInfoByUserAddressBookId(Long shopId, Long addressBookId) throws Exception {
        AddressBook addressBook = TypeR.extract(userFeignService.getUserAddressBookById(addressBookId));
        return this.getShopDeliveryInfoByUserGeoCoordinates(shopId, new GeoCoordinates(addressBook.getLongitude(), addressBook.getLatitude()));
    }

    private List<List<TimeRange>> processedToShopPredetermineDaysValidPeriod(Integer advanceBookingDays,
                                                                             Map<Integer, List<TimeRange>> shopWeekPredetermineValidPeriod,
                                                                             Map<LocalDateRange, List<TimeRange>> shopDatePredetermineValidPeriod) {
        // 初始化一个指定大小(advanceBookingDays+1)但元素暂空的List
        List<List<TimeRange>> shopPredetermineDaysValidPeriod = new ArrayList<>(Collections.nCopies(advanceBookingDays + 1, null));

        for (int i = 0; i < shopPredetermineDaysValidPeriod.size(); i++) {
            // week-times ==> shopPredetermineDaysValidPeriod
            // 当前i对应的LocalDate
            LocalDate currentLocalDate = LocalDate.now().plusDays(i);
            // 这里的week与shopWeekPredetermineValidPeriod中的key一样的，都是从1-7,这是要保证的
            int currentLocalDateWeek = currentLocalDate.getDayOfWeek().getValue();
            List<TimeRange> times = shopWeekPredetermineValidPeriod.get(currentLocalDateWeek);
            shopPredetermineDaysValidPeriod.set(i, times);

            // date-times ==> shopPredetermineDaysValidPeriod
            for (LocalDateRange localDateRange : shopDatePredetermineValidPeriod.keySet()) {
                if (localDateRange.in(currentLocalDate)) {
                    // 特殊的date-times比week-times优先级高，会将前置覆盖
                    shopPredetermineDaysValidPeriod.set(i, shopDatePredetermineValidPeriod.get(localDateRange));
                    break;
                }
            }
        }
        return shopPredetermineDaysValidPeriod;
    }

    /**
     * 获取店铺指定日期段-有效可预定的时间段
     *
     * @param shopBusinessRules 商店商业规则
     * @return {@link Map}<{@link String}, {@link List}<{@link String}>>
     */
    private Map<LocalDateRange, List<TimeRange>> getShopDatePredetermineValidPeriod(List<ShopBusinessRules> shopBusinessRules) {
        if (shopBusinessRules == null || shopBusinessRules.isEmpty()) return new HashMap<>();
        List<ShopBusinessRules> shopBusinessDateRules = shopBusinessRules.stream().filter(shopBusinessRule -> shopBusinessRule.getType().equals(ShopBusinessRuleType.DATE_TIMES.getCode())).collect(Collectors.toList());
        if (shopBusinessDateRules.isEmpty()) return new HashMap<>();
        Map<LocalDateRange, List<TimeRange>> result = new HashMap<>();
        for (ShopBusinessRules shopBusinessDateRule : shopBusinessDateRules) {
            LocalDateRange localDateRange = new LocalDateRange(shopBusinessDateRule.getStartDate(), shopBusinessDateRule.getEndDate());
            // 只支持绝对范围
            if (!localDateRange.isAbsoluteRange()) continue;
            // 这里当 `shopBusinessDateRule.getTimes()`这空时不能排除该项， 因为当为空时有含意，表示该日期段内不营业（休息），它的意义是在放假前设置，如果没有，那需要修改week-times，但它是日常的设置，放假后还需要设置回来。为避免麻烦需要“特殊规则”
            List<TimeRange> times = new ArrayList<>();
            if (!StringUtil.isBlank(shopBusinessDateRule.getTimes())) {
                times = Arrays.stream(shopBusinessDateRule.getTimes().split(",")).map(timeRangeStr -> TimeRange.constructor(timeRangeStr, CommonSeparatorConstants.TIME_RANGE_SEPARATOR)).collect(Collectors.toList());
            }
            result.put(localDateRange, times);
        }
        return result;
    }


    /**
     * 将店铺经营规则解析出“周-times”列表
     *
     * @param shopBusinessRules 商店商业规则
     * @return {@link Map}<{@link Integer}, {@link List}<{@link String}>>
     */
    private Map<Integer, List<TimeRange>> getShopWeekPredetermineValidPeriod(List<ShopBusinessRules> shopBusinessRules) {
        if (shopBusinessRules == null || shopBusinessRules.isEmpty()) return new HashMap<>();
        List<ShopBusinessRules> shopBusinessWeekRules = shopBusinessRules.stream().filter(shopBusinessRule -> shopBusinessRule.getType().equals(ShopBusinessRuleType.WEEK_TIMES.getCode())).collect(Collectors.toList());
        Map<Integer, List<TimeRange>> weekTimesMap = new HashMap<>();
        for (ShopBusinessRules shopBusinessWeekRule : shopBusinessWeekRules) {
            String weeksStr = shopBusinessWeekRule.getWeeks();
            String[] weeks = weeksStr.split(CommonSeparatorConstants.MULTIPLE_SEPARATOR);
            if (weeks.length == 0 || StringUtil.isBlank(shopBusinessWeekRule.getTimes())) continue;
            for (String week : weeks) {
                List<TimeRange> timeRanges = Arrays.stream(shopBusinessWeekRule.getTimes().split(CommonSeparatorConstants.MULTIPLE_SEPARATOR)).map(timeRangeStr -> TimeRange.constructor(timeRangeStr, CommonSeparatorConstants.TIME_RANGE_SEPARATOR)).collect(Collectors.toList());
                weekTimesMap.put(Integer.valueOf(week), timeRanges);
            }
        }
        return weekTimesMap;
    }


    @Override
    public void changeShopStatus(Integer businessStatus) {
        LambdaUpdateWrapper<Shop> updateShopBusinessStatusWrapper = Wrappers.lambdaUpdate(Shop.class)
                .eq(Shop::getId, ShopModuleTokenDataProvider.getShopId())
                .set(Shop::getBusinessStatus, businessStatus);
        if (!this.update(updateShopBusinessStatusWrapper)) {
            throw new RuntimeException("更新状态失败！");
        }
    }

    @Override
    public ShopForUserOrderMapVO selectShopInfoForUserOrderMap(Long shopId) {
        return this.baseMapper.selectShopInfoForUserOrderMap(shopId);
    }

    @Override
    public Page<ShopSurfaceInfoVO> followListPage(FollowShopPageDTO pageDTO) {
        Page<ShopSurfaceInfoVO> page = new Page<>(pageDTO.getCurrentPage(), pageDTO.getPageSize());
        this.baseMapper.followShopPage(page, pageDTO, UserProvider.currentUserId());
        List<ShopSurfaceInfoVO> pageList = page.getRecords();
        if (pageList == null || pageList.isEmpty()) {
            return page;
        }
        // 声明需要注入的店铺数据
        Map<ShopInfoElementEnum, LambdaStructureParser.NAHRFunction<ShopSurfaceInfoVO, Object>> shopInfoElementEnum = new HashMap<>();
        shopInfoElementEnum.put(ShopInfoElementEnum.BETTER_DISPLAY_DISH, ShopSurfaceInfoVO::getDishList);
        shopInfoElementEnum.put(ShopInfoElementEnum.PREDETERMINE_FLAG, ShopSurfaceInfoVO::getBookable);
        shopInfoElementEnum.put(ShopInfoElementEnum.STATISTICAL_INFO, ShopSurfaceInfoVO::getStatistical);
        shopInfoElementEnum.put(ShopInfoElementEnum.ACTIVITY_INFO, ShopSurfaceInfoVO::getActivityInstanceTitleForArrayList);

        // 收集
        shopInfoElementCollect(pageList, shopInfoElementEnum);
        return page;
    }
}
